package org.hawk.gwt.ppc.parser.impl;

import static org.hawk.gwt.ppc.parser.impl.DescriptorParserUtils.expect;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hawk.gwt.ppc.parser.AnnotationDescriptor;
import org.hawk.gwt.ppc.parser.ClassDescriptor;
import org.hawk.gwt.ppc.parser.CompilationUnitDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.DescriptorVisitor;
import org.hawk.gwt.ppc.parser.ImportsDescriptor;
import org.hawk.gwt.ppc.parser.JavaKeywords;
import org.hawk.gwt.ppc.parser.PackageDescriptor;
import org.hawk.gwt.ppc.parser.Token;
import org.hawk.gwt.ppc.utils.Name;
import org.hawk.gwt.ppc.utils.PreprocessorException;

/**
 * Parses whole compilation unit.
 * Compilation unit contains:
 *  - package declaration
 *  - imports section
 *  - several classes
 *  All elements are optional.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
final class CompilationUnitDescriptorParser implements DescriptorParser {

	public boolean accepts(Token token) {
		return true;
	}

	public CompilationUnitDescriptor parse(TokenBasedJavaParser parser, TokenSource tokenizer, Object...params)
			throws PreprocessorException {
		PackageDescriptor packageDescriptor = null;
		ImportsDescriptor importsDescriptor = null;
		List<Descriptor> descriptors = new ArrayList<Descriptor>(4);
		List<ClassDescriptor> classes = new ArrayList<ClassDescriptor>(1);
		List<AnnotationDescriptor> annotations = new ArrayList<AnnotationDescriptor>(2);
		
		Descriptor result;
		Token last = null;
		
		if (AnnotationDescriptorParser.parseAnnotations(parser, tokenizer, annotations)) {
			result = DescriptorType.CLASS.parse(parser, tokenizer, 
					Collections.emptyList(), JavaKeywords.ANNOTATION);
		} else {
			result = expect(parser, tokenizer, descriptors, DescriptorType.PACKAGE, 
					DescriptorType.IMPORTS, DescriptorType.CLASS);
		}
		
		if (result instanceof PackageDescriptor) {
			packageDescriptor = (PackageDescriptor) result;
			last = ((AbstractDescriptor)result).getLastToken();
			result = expect(parser, tokenizer, descriptors, 
					DescriptorType.IMPORTS, DescriptorType.CLASS, DescriptorType.EOF);
		} else {
			packageDescriptor = PackageDescriptorParser.createDefaultPackage();
			descriptors.add(packageDescriptor);
		}
		
		if (result instanceof ImportsDescriptor) {
			importsDescriptor = (ImportsDescriptor) result;
			last = ((AbstractDescriptor)result).getLastToken();
			result = expect(parser, tokenizer, descriptors, 
					DescriptorType.CLASS, DescriptorType.EOF);
		} else {
			importsDescriptor = ImportsDescriptorParser.createEmptyImportsDescriptor();
			descriptors.add(importsDescriptor);
		}
		
		while (result != null) {
			last = ((AbstractDescriptor)result).getLastToken();
			classes.add((ClassDescriptor) result);
			result = expect(parser, tokenizer, descriptors, 
					DescriptorType.CLASS, DescriptorType.EOF);
		}
		
		return new CompilationUnitDescriptorImpl(last, packageDescriptor, importsDescriptor, classes, descriptors);
	}

	/**
	 * 
	 * @author alex.bereznevatiy@gmail.com
	 *
	 */
	private static class CompilationUnitDescriptorImpl 
			extends AbstractDescriptorContainer 
			implements CompilationUnitDescriptor {
		
		private PackageDescriptor packageDescriptor;
		
		private ImportsDescriptor importsDescriptor;
		
		private List<ClassDescriptor> classes;

		CompilationUnitDescriptorImpl(Token lastToken, 
				PackageDescriptor packageDescriptor,
				ImportsDescriptor importsDescriptor,
				List<ClassDescriptor> classes, 
				List<Descriptor> descriptors) {
			super(packageDescriptor.getFirstToken(), lastToken, descriptors);
			
			this.packageDescriptor = packageDescriptor;
			this.importsDescriptor = importsDescriptor;
			this.classes = classes;
		}

		/*
		 * (non-Javadoc)
		 * @see org.hawk.gwt.ppc.parser.CompilationUnitDescriptor#getTopClasses()
		 */
		public List<ClassDescriptor> getTopClasses() {
			return classes;
		}

		/*
		 * (non-Javadoc)
		 * @see org.hawk.gwt.ppc.parser.CompilationUnitDescriptor#getImports()
		 */
		public ImportsDescriptor getImports() {
			return importsDescriptor;
		}

		/*
		 * (non-Javadoc)
		 * @see org.hawk.gwt.ppc.parser.CompilationUnitDescriptor#getPackage()
		 */
		public PackageDescriptor getPackage() {
			return packageDescriptor;
		}

		/*
		 * (non-Javadoc)
		 * @see org.hawk.gwt.ppc.parser.Descriptor#visit(org.hawk.gwt.ppc.parser.DescriptorVisitor)
		 */
		public void visit(DescriptorVisitor visitor) throws PreprocessorException {
			visitor.visit(this);
			
			packageDescriptor.visit(visitor);
			importsDescriptor.visit(visitor);
			
			for (ClassDescriptor clazz : classes) {
				clazz.visit(visitor);
			}
		}

		public String getName() {
			for (ClassDescriptor clazz : classes) {
				if (clazz.getModifiers().contains(JavaKeywords.PUBLIC)) {
					return clazz.getName();
				}
			}
			if (!classes.isEmpty()) {
				return classes.get(0).getName();
			}
			return Name.getChildJavaName(packageDescriptor.getPackageName(), "package-info");
		}
	}
}